#include "lime.h"
#include <asm/uaccess.h> /* for KERNEL_DS and get_fs() */
#include <linux/slab.h>

int write_vaddr_disk(void *, size_t);
int setup_disk(void);
void cleanup_disk(void);

static void disable_dio(void);

static struct file * f = NULL;
static struct file * g = NULL;
extern char * path;
extern char * rcpath;
extern int dio;
extern int part_no;

static void disable_dio() {
	DBG("Direct IO may not be supported on this file system. Retrying.");
	dio = 0;
	cleanup_disk();
	setup_disk();
}

int my_strlen( char * s ) {
    int l = 0;
    while( *s ) {
        l++;
        s++;
    }
    return l;
}

int setup_disk() {
    
    int img_len = my_strlen( path );
    int  rc_len = my_strlen( rcpath );
    
    char * img_fn = kmalloc( img_len + 10, GFP_NOFS );
    char *  rc_fn = kmalloc(  rc_len + 10, GFP_NOFS );
    
	mm_segment_t fs;
	int err;
	
    sprintf( img_fn, "%s.%04d",   path, part_no );
    sprintf(  rc_fn, "%s.%04d", rcpath, part_no );

	fs = get_fs();
	set_fs( KERNEL_DS );
	
	if( dio ) {
		f = filp_open( img_fn, O_WRONLY | O_CREAT | O_LARGEFILE | O_SYNC | O_DIRECT, 0444 );
        g = filp_open(  rc_fn, O_WRONLY | O_CREAT | O_LARGEFILE | O_SYNC | O_DIRECT, 0444 );
	}

	if( !dio || (f == ERR_PTR(-EINVAL)) ) {
		DBG("Direct IO Disabled");
		f = filp_open( img_fn, O_WRONLY | O_CREAT | O_LARGEFILE, 0444 );
		g = filp_open(  rc_fn, O_WRONLY | O_CREAT | O_LARGEFILE, 0444 );
		dio = 0;
	}

	if( !f || IS_ERR(f) ) {
		DBG("Error opening file %ld", PTR_ERR(f));
		set_fs(fs);
        kfree( img_fn );
        kfree(  rc_fn );
		err = (f) ? PTR_ERR(f) : -EIO;
		f = NULL;
		return err;
	}
	if( !g || IS_ERR(g) ) {
		DBG("Error opening file %ld", PTR_ERR(g));
		set_fs(fs);
		kfree( img_fn );
        kfree(  rc_fn );
		err = (g) ? PTR_ERR(g) : -EIO;
		g = NULL;
		return err;
	}

	set_fs(fs);
	kfree( img_fn );
    kfree(  rc_fn );
    
	return 0;
}

void cleanup_disk() {
	mm_segment_t fs;
	
	fs = get_fs();
	set_fs(KERNEL_DS);
	if( f ) {
	    filp_close( f, NULL );
    }
    if( g ) {
        filp_close( g, NULL );
    }
	set_fs(fs);
}

int write_vaddr_disk(void * v, size_t is) {
	mm_segment_t fs;

	long s;

	fs = get_fs();
	set_fs(KERNEL_DS);
	    
	s = vfs_write(f, v, is, &f->f_pos);					
	
	set_fs(fs);

	if (s != is && dio) {
		disable_dio();
		return write_vaddr_disk(v, is);
	}

	return s;
}

int write_count( int krc, int urc ) {
	mm_segment_t fs;

	long s;
    long t;
    char s_not_ok;
    char t_not_ok;
    char not_ok;

	fs = get_fs();
	set_fs(KERNEL_DS);
	    
	s = vfs_write( g, (void*)&krc, sizeof( int ), &g->f_pos);
    t = vfs_write( g, (void*)&urc, sizeof( int ), &g->f_pos);
	
	set_fs(fs);

    s_not_ok = s != sizeof( int );
    t_not_ok = t != sizeof( int );

    not_ok = s_not_ok || t_not_ok;
    
	if( not_ok && dio) {
		disable_dio();
		return write_count( krc, urc );
	}

	return s;
}

